﻿
Imports System.Text

Public Module _debug

    Public Function callstack(Optional ByVal removeblanks As Boolean = False, _
                              Optional ByVal separator As Char = character.colon) As String
#If Not PocketPC AndAlso Not Smartphone Then
        Dim rtn As String = Nothing
        rtn = System.Environment.StackTrace()
        If removeblanks Then
            rtn = strrplc(rtn, "   ", emptyString)
            rtn = strrplc(rtn, newline.incode(), separator)
        End If
        Return rtn
#Else
        Return "##CANNOTTRACE##"
#End If
    End Function

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function callingcode(Optional ByVal additionalJump As Int32 = 0) As String
#If Not (PocketPC OrElse Smartphone) Then
        Dim rtn As StringBuilder = Nothing
        rtn = New StringBuilder()
        Dim callStack As StackFrame = Nothing
        Dim jump As Int32 = 1

        jump += additionalJump
        If isdebugmode() Then
            callStack = New StackFrame(jump, True)
            If isemptystring(callStack.GetFileName()) Then
                rtn.Append("##MISSING_PDB##")
            Else
                rtn.Append(callStack.GetFileName())
                rtn.Append(character.leftBracket)
                rtn.Append(Convert.ToString(callStack.GetFileLineNumber()))
                rtn.Append(character.rightBracket)
            End If
            rtn.Append(character.colon)
        Else
            callStack = New StackFrame(jump, False)
        End If
        rtn.Append(callStack.GetMethod.Module.Name())
        rtn.Append(character.dot)
        rtn.Append(callStack.GetMethod.DeclaringType.FullName())
        rtn.Append(character.dot)
        rtn.Append(callStack.GetMethod.Name())

        Return Convert.ToString(rtn)
#Else
        Return "##CANNOTTRACE##"
#End If
    End Function

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function invokingcode(Optional ByVal additionalJump As Int32 = 0) As String
        Return callingcode(additionalJump + 1)
    End Function

    'always called by debugassert & assertImpl, so the additionaljump is 1 as default value
    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Private Sub assertRaiseError(ByVal assertlevel As String, Optional ByVal msg As String = Nothing, _
                                 Optional ByVal additionaljump As Int32 = 1)
        If msg Is Nothing Then
            msg = assertlevel + " failed."
        Else
            msg = assertlevel + " failed, " + msg
        End If
        msg += newline.incode() + callstack()
        raiseError(msg, errorHandle.errorType.critical, additionaljump:=additionaljump + 1)
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function debugassert(ByVal v As Boolean, Optional ByVal msg As String = Nothing) As Boolean
        If isdebugmode() Then
            Return assertImpl(v, msg, 1)
        Else
            If Not v Then
                assertRaiseError("debugassert", msg, 1)
                Return False
            Else
                Return True
            End If
        End If
    End Function

    Private PAUSEWHENINDEBUGMODE As Boolean = True

    Public Sub setstrongassert()
        PAUSEWHENINDEBUGMODE = False
    End Sub

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function assert(ByVal v As Boolean, Optional ByVal msg As String = Nothing) As Boolean
        Return assertImpl(v, msg, 1)
    End Function

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function assert(ByVal v As Boolean, ByVal ParamArray p() As Object) As Boolean
        Return assertImpl(v, New lazyStrCat(p), 1)
    End Function

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function assert_return(Of T)(ByVal v As Boolean, ByVal i As T, Optional ByVal msg As String = Nothing) As T
        assert(v, msg)
        Return i
    End Function

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function assert_return(Of T)(ByVal v As _do(Of Boolean), ByVal i As T,
                                        Optional ByVal msg As String = Nothing) As _do(Of T)
        Return assert_do(v,
                         Function() As T
                             Return i
                         End Function,
                         msg)
    End Function

    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Public Function assert_do(Of T)(ByVal v As _do(Of Boolean), ByVal i As _do(Of T),
                                    Optional ByVal msg As String = Nothing) As _do(Of T)
        Return Function() As T
                   assert(do_(v, False), msg)
                   Return do_(i, Nothing)
               End Function
    End Function

    'always called by assert & debugassert, so additionaljump default to 1
    <Runtime.CompilerServices.MethodImpl(Runtime.CompilerServices.MethodImplOptions.NoInlining)> _
    Private Function assertImpl(Of T)(ByVal v As Boolean, ByVal msg As T, _
                                      Optional ByVal additionaljump As Int32 = 1) As Boolean
        If Not v Then
            assertRaiseError("assert", Convert.ToString(msg), additionaljump + 1)
            If isdebugmode() AndAlso PAUSEWHENINDEBUGMODE Then
#If RETIRED Then
                If errorHandle.mode = errorHandle.displayMode.console Then
                    pause()
                End If
#End If
                If Debugger.IsAttached() Then
                    Debugger.Break()
                Else
                    Debugger.Launch()
                End If
            Else
                finishmainBase()
                forceQuit()
            End If

            Return False
        Else
            Return True
        End If
    End Function

    Private INDEBUGMODE As Boolean = False
    Private NOTINDEBUGMODE As Boolean = False

    Public Sub set_debug_mode()
        INDEBUGMODE = True
        NOTINDEBUGMODE = False
    End Sub

    Public Sub set_not_debug_mode()
        INDEBUGMODE = False
        NOTINDEBUGMODE = True
    End Sub

    Public Sub set_normal_mode()
        INDEBUGMODE = False
        NOTINDEBUGMODE = False
    End Sub

    Public Function isreleasebuild() As Boolean
        Return Not isdebugbuild()
    End Function

    Public Function isdebugbuild() As Boolean
#If DEBUG Then
        Return True
#Else
        Return False
#End If
    End Function

    Public Function isdebugmode() As Boolean
        If INDEBUGMODE Then
            Return True
        ElseIf NOTINDEBUGMODE Then
            Return False
        Else
            Return isdebugbuild()
        End If
    End Function

    Public Sub debugpause(Optional ByVal needKeyInput As Boolean = True)
        If isdebugmode() Then
            If needKeyInput Then
                pause()
            Else
                Dim i As Int32
                Console.Write("wait 30 seconds")
                For i = 0 To 29
                    sleep(1000)
                    Console.Write(".")
                Next
                Console.Write(character.return)
            End If
        End If
    End Sub

    Public Sub pause(Optional ByVal showMessage As Boolean = True)
        On Error Resume Next
        If showMessage Then
            Console.Error.Write("press any key to continue")
        End If

#If Not (PocketPC OrElse Smartphone) Then
        Console.ReadKey()
#Else
        Console.In.Read()
#End If
        If showMessage Then
            Console.Error.Write(character.return)
        End If
    End Sub

    Public Function getInvokeMethodName(ByVal invoke As [Delegate]) As String
        Return invokeMethodName(invoke)
    End Function

    Public Function invokeMethodName(ByVal invoke As [Delegate]) As String
#If PocketPC OrElse Smartphone Then
        Return "#CANNOT_GET_INVOKE_NAME#"
#Else
        Try
            Return invoke.Method.Name
        Catch
            Return "#CANNOT_GET_INVOKE_NAME#"
        End Try
#End If
    End Function

    Public Function invokeMethodIdentity(ByVal invoke As [Delegate]) As String
        Try
            Dim rtn As StringBuilder = Nothing
            rtn = New StringBuilder()
            If Not invoke.Target Is Nothing Then
                rtn.Append(invoke.Target.GetType().FullName) _
                   .Append(character.colon)
            End If
            rtn.Append(invoke.Method.DeclaringType.FullName) _
               .Append(character.dot) _
               .Append(invoke.Method.Name)
            If isdebugmode() Then
                rtn.Append(character.at) _
                   .Append(invoke.Method.Module.FullyQualifiedName)
            End If
            Return Convert.ToString(rtn)
        Catch
            Return "#CANNOT_GET_INVOKE_IDENTITY#"
        End Try
    End Function

    Sub New()
        'dirty hack here, for vs release/debug build
#If Not (PocketPC OrElse Smartphone) Then
        If strsame(envs.executingDirectory(), "release", False) OrElse _
           strsame(envs.executingDirectory(), "debug", False) Then
            set_debug_mode()
        End If
#End If
    End Sub

End Module
